//
// Created by DELL on 2023/12/13.
//

#include <iostream>
#include "WglContext.h"

WglContext::WglContext(int width, int height)
    :width(width), height(height)
{
    PIXELFORMATDESCRIPTOR mPfd = {sizeof(PIXELFORMATDESCRIPTOR),
                                  1,
                                  PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Flags
                                  PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette.
                                  32,            // Colordepth of the framebuffer.
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0,
                                  24, // Number of bits for the depthbuffer
                                  8,  // Number of bits for the stencilbuffer
                                  0,  // Number of Aux buffers in the framebuffer.
                                  PFD_MAIN_PLANE,
                                  0,
                                  0,
                                  0,
                                  0};


    int flags{0};
#ifndef NDEBUG
    flags |= WGL_CONTEXT_DEBUG_BIT_ARB;
#else
#endif

    this->hwnd = createWindow(width, height);
    SetPropW(this->hwnd, L"context", this);
    this->hdc = GetDC(hwnd);

    if (hdc == nullptr)
    {
        throw std::runtime_error(" get hdc failed!");
    }

    int pixelFormat = ChoosePixelFormat(hdc, &mPfd);
    SetPixelFormat(hdc, pixelFormat, &mPfd);

    // We need a tmp context to retrieve and call wglCreateContextAttribsARB.
    HGLRC tempContext = wglCreateContext(hdc);
    if (!wglMakeCurrent(hdc, tempContext))
    {
        throw std::runtime_error("wglMakeCurrent() failed. tempContext");
    }

    if (gladLoadGL() == 0)
    {
        throw std::runtime_error("gladLoadGL failed");
    }

    int attribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB, 4, WGL_CONTEXT_MINOR_VERSION_ARB, 5,
                     WGL_CONTEXT_FLAGS_ARB, flags,
                     WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
                     0, 0};
    auto glVersion = glGetString(GL_VERSION);
    if (glVersion != nullptr)
    {
        int32_t major = 4, minor = 5;
        if(sscanf_s(reinterpret_cast<const char*>(glVersion), "%d.%d", &major, &minor))
        {
            attribs[1] = major;
            attribs[3] = minor;
        }
    }

    auto  wglCreateContextAttribs = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
    context = wglCreateContextAttribs(hdc, nullptr, attribs);
    if (!context)
    {
        throw std::runtime_error("wglCreateContextAttribs() failed");
    }
    wglMakeCurrent(hdc, context);
    wglDeleteContext(tempContext);

    flags = 0;
    glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
    if (flags & (int)GL_CONTEXT_FLAG_DEBUG_BIT)
    {
        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        glDebugMessageCallback(&WglContext::debugOutputCallback, this);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE,GL_DONT_CARE, 0, nullptr, GL_TRUE);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION , 0, nullptr, GL_FALSE);

        printf("OpenGL Debug ContextWGL\n");
    }

    printf("OpenGL Vendor: %s\n", (const char*)glGetString(GL_VENDOR));
    printf("OpenGL Version: %s\n", (const char*)glGetString(GL_VERSION));
    printf("OpenGL Language: %s\n", (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
    printf("OpenGL Renderer: %s\n", (const char*)glGetString(GL_RENDERER));
    std::cout << std::endl;

    wglMakeCurrent(nullptr, nullptr);
}

WglContext::~WglContext()
{
    wglMakeCurrent(nullptr, nullptr);
    if (context)
    {
        wglDeleteContext(context);
        context = nullptr;
    }

    if (this->hdc)
    {
        ReleaseDC(hwnd, hdc);
        this->hdc = nullptr;
    }

    if (this->hwnd)
    {
        DestroyWindow(hwnd);
        this->hwnd = nullptr;
    }
}

void WglContext::makeCurrent()
{
    wglMakeCurrent(hdc, context);
}

std::shared_ptr<WglContext> WglContext::create(int width, int height)
{
    struct EnableMakeShared : public WglContext
    {
        EnableMakeShared(int width, int height) : WglContext(width, height){}
    };
    return std::make_shared<EnableMakeShared>(width, height);
}

void WglContext::bind()
{
    this->lock.lock();
    this->makeCurrent();
}

void WglContext::unbind()
{
    wglMakeCurrent(nullptr, nullptr);
    this->lock.unlock();
}

bool WglContext::update()
{
    if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
            return false;
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return true;
}

void WglContext::swapBuffer()
{
    SwapBuffers(this->hdc);
}

void WglContext::setSwapInterval(int interval)
{
    auto wglSwapInterval = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");
    if (wglSwapInterval != nullptr)
    {
        wglSwapInterval(interval); // VSync 垂直同步 Vertical sync
    }
}

void APIENTRY WglContext::debugOutputCallback(GLenum source, GLenum type, unsigned int id, GLenum severity,
                                                 GLsizei length, const char *message, const void *userParam)
{
    // ignore non-significant error/warning codes
    if(id == 131169 || id == 131185 || id == 131218 || id == 131204 || id == 8 || id == 0x20052)
    {
        return;
    }

    const char* srcStr;
    switch (source)
    {
    case GL_DEBUG_SOURCE_API:             srcStr = "Source: API"; break;
    case GL_DEBUG_SOURCE_WINDOW_SYSTEM:   srcStr = "Source: Window System"; break;
    case GL_DEBUG_SOURCE_SHADER_COMPILER: srcStr = "Source: Shader Compiler"; break;
    case GL_DEBUG_SOURCE_THIRD_PARTY:     srcStr = "Source: Third Party"; break;
    case GL_DEBUG_SOURCE_APPLICATION:     srcStr = "Source: Application"; break;
    case GL_DEBUG_SOURCE_OTHER:           srcStr = "Source: Other"; break;
    default: srcStr = "";
    }

    const char* typeStr;
    switch (type)
    {
    case GL_DEBUG_TYPE_ERROR:               typeStr = "Type: Error"; break;
    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeStr = "Type: Deprecated Behaviour"; break;
    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  typeStr = "Type: Undefined Behaviour"; break;
    case GL_DEBUG_TYPE_PORTABILITY:         typeStr = "Type: Portability"; break;
    case GL_DEBUG_TYPE_PERFORMANCE:         typeStr = "Type: Performance"; break;
    case GL_DEBUG_TYPE_MARKER:              typeStr = "Type: Marker"; break;
    case GL_DEBUG_TYPE_PUSH_GROUP:          typeStr = "Type: Push Group"; break;
    case GL_DEBUG_TYPE_POP_GROUP:           typeStr = "Type: Pop Group"; break;
    case GL_DEBUG_TYPE_OTHER:               typeStr = "Type: Other"; break;
    default: typeStr = "";
    }

    const char* severityStr;
    switch (severity)
    {
    case GL_DEBUG_SEVERITY_HIGH:         severityStr = "Severity: high"; break;
    case GL_DEBUG_SEVERITY_MEDIUM:       severityStr = "Severity: medium"; break;
    case GL_DEBUG_SEVERITY_LOW:          severityStr = "Severity: low"; break;
    case GL_DEBUG_SEVERITY_NOTIFICATION: severityStr = "Severity: notification"; break;
    default: severityStr = "";
    }

    printf("Debug message (0x%x): %s, %s, %s, %s\n", id, message, srcStr, typeStr, severityStr);
    std::cout << std::endl;
}

HWND WglContext::createWindow(int width, int height)
{
    static HINSTANCE hInstance = nullptr;
    constexpr const wchar_t* windowClassName = L"SampleWindow";

    if (hInstance == nullptr)
    {
        hInstance = GetModuleHandleW(nullptr);
        // 注册窗口类
        WNDCLASSW wc = { 0 };
        wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
        wc.lpfnWndProc = &WglContext::WindowProc; // 指定窗口过程函数
        wc.hInstance = hInstance; // 指定应用程序实例句柄
        wc.lpszClassName = windowClassName; // 指定窗口类名
        wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
        RegisterClassW(&wc);
    }

    // 创建窗口
    HWND hwnd = CreateWindowExW( 0,
        windowClassName, // 窗口类名
        L"OpenGL", // 窗口标题
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, // 窗口样式，包括可调整大小和可见
        CW_USEDEFAULT, // 窗口水平位置，由系统自动选择
        CW_USEDEFAULT, // 窗口垂直位置，由系统自动选择
        width, // 窗口宽度，由系统根据窗口类和样式自动计算
        height, // 窗口高度，由系统根据窗口类和样式自动计算
        nullptr, // 父窗口句柄，没有则为NULL
        nullptr, // 窗口菜单句柄，没有则为NULL
        hInstance, // 应用程序实例句柄
        nullptr // 附加参数，没有则为NULL
    );
    return hwnd;
}

LRESULT WglContext::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    auto context = (WglContext*)GetPropW(hwnd, L"context");
    switch (uMsg)
    {
    case WM_CREATE:
    {
        return 0;
    }
    case WM_SIZE:
    {
        int width = LOWORD(lParam);
        int height = HIWORD(lParam);
        if (context != nullptr)
        {
            context->width = width;
            context->height = height;
            if (context->resizeCallback != nullptr)
            {
                context->resizeCallback(width, height);
            }
        }
        break;
    }
    case WM_DESTROY: // 当窗口被销毁时，发送退出消息
        context->closed = true;
        PostQuitMessage(0);
        return 0;
    case WM_PAINT: // 当窗口需要绘制时，使用默认的绘制函数
    {
//        PAINTSTRUCT ps;
//        HDC hdc = BeginPaint(hwnd, &ps);
//        // All painting occurs here, between BeginPaint and EndPaint.
//        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_GRAYTEXT +1));
//        EndPaint(hwnd, &ps);
//        return 0;
        break;
    }
    default:
        break;
    }
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
